跳到主要内容

文章 | Servo 研究笔记(一)

提示

本文首发于知乎。

本文旨在分享本人在研究 servo 引擎时所得到的理解,但其中可能存在不准确或遗漏之处。

Servo 的架构

Servo 中的每个页面称为一个“系列”(constellation1),它封装了会话历史,了解帧树(frame tree)中的所有帧,并且是每个包含帧的管线(pipeline)的所有者2

TODO:frame tree 是什么?

Servo 的页面渲染分为四个步骤:脚本执行、布局、渲染、合成,由系列发起的管线执行3

  • 脚本执行阶段4负责创建和管理 DOM,并执行 JavaScript 引擎。它接收来自多个来源的事件,包括导航事件,并相应地路由它们。当脚本需要布局信息时,它会向布局任务发送请求。
  • 布局阶段5对 DOM 进行快照,计算样式并构建主要的布局数据结构——流树(flow tree6)。流树用于计算节点的布局,并从中构建显示列表(display list7),该列表被发送到渲染任务中。
  • 渲染阶段8接收显示列表,并将可见部分渲染到一个或多个图块中。渲染任务可能会并行处理。
  • 合成阶段9将来自渲染器的图块合成并发送到屏幕上进行显示。作为 UI 线程,合成器也是 UI 事件的第一个接收者,这些事件通常会立即发送到内容进行处理。某些事件(例如滚动事件)最初可能由合成器处理以获得响应。

TODO:原文中“路由”(routes them as necessary)是什么意思?

TODO:脚本会使用布局的信息,是否说明这两个阶段是交错进行的?

Servo 中的 DOM 和显示列表是两个核心的数据结构。

DOM 以树形结构实现,具有版本化节点,使用写时复制和垃圾回收机制进行管理。DOM 节点的生命周期由 JavaScript 垃圾回收器进行管理,JavaScript 可以直接访问节点,无需借助 XPCOM 或类似机制。

TODO:这是否意味着无法剥离 Script 而只执行其他三个阶段?

显示列表是布局任务创建的一系列有序的高级绘图命令集合,按照 z-index 排序。Servo 的显示列表是不可变的,因此多个渲染器可以并发共享访问。

         +---------------+
+----+ Constellation |
| +---------------+
|
| Content +---------------+ tile
| +-->+ Render Thread +--------+
v | +---------------+ |
+---+----+ DOM +--------+ display list | | +------------+
| Script +------>+ Layout +---------------+ +-->+ Compositor |
+--------+ +--------+ | | +------------+
| +---------------+ tile |
+-->+ Render Thread +--------+
+---------------+

构建系统

Servo 有自己的构建系统 Mach。Mach 由 Python 编写,负责处理平台和环境依赖,解析用户配置,然后生成 cargo 命令并调用10

更准确地说,Mach 是一个编写命令行程序的辅助库11,是为 servo 的构建系统12编写的。

Servo 是作为一系列 library 构建的13,其提供 API,窗口前端14调用这些库,提供显示和用户交互。

我在 Windows 上试图编译 Servo 时失败了(mozjs_sys 的环境问题,果然 C++ 混沌邪恶),遂转在 Ubuntu 上编译。

运行 ./mach build -d 可以编译一个图形界面的 demo 浏览器程序,编译完成后可以用 ./mach run <URL> 测试运行。

自己测试的结果(commit 0cffe55)如下:

网站结果
百度失败(控制台可见 js 输出,网页显示为白屏)
谷歌失败(网络问题)
Python http.server成功
rustdoc失败(js 执行出错)
可见 servo 对 js 的支持不是很好。

API

使用 ./mach build -dv 可以看到 mach 构建的 cargo 命令:

rustup run --install nightly-2023-02-01 cargo build --manifest-path /home/wybxc/servo/ports/winit/Cargo.toml --features media-gstreamer native-bluetooth egl layout-2013 --timings -v

可以看出,默认构建的是 winit 前端15。接下来,就可以参考这个 crate 中使用 servo API 的方式,分析 servo API 的构成和作用。

参考


  1. https://github.com/servo/servo/blob/master/components/constellation/lib.rs
  2. https://github.com/servo/servo/blob/master/docs/glossary.md
  3. https://github.com/servo/servo/wiki/Design
  4. https://github.com/servo/servo/blob/master/components/script/script_thread.rs
  5. https://github.com/servo/servo/blob/master/components/layout_thread/lib.rs
  6. https://github.com/servo/servo/blob/master/components/layout/flow.rs
  7. https://github.com/servo/servo/blob/master/components/gfx/display_list/mod.rs
  8. https://github.com/servo/servo/blob/master/components/gfx/paint_task.rs
  9. https://github.com/servo/servo/blob/master/components/compositing/compositor.rs
  10. https://github.com/servo/servo/blob/master/python/mach_bootstrap.py
  11. https://github.com/servo/servo/tree/master/python/mach
  12. https://github.com/servo/servo/tree/master/python/servo
  13. https://github.com/servo/servo/tree/master/components
  14. https://github.com/servo/servo/tree/master/ports
  15. https://github.com/servo/servo/tree/master/ports/winit
Loading...